const fs = require('fs');
const os = require('os');
const fsPromise = fs.promises;
const executor = require('child_process');
const config = require('./config');
const iniDic = require('./iniDic');
const luajs = require('lua.js');
const https = require('https');

process.on('exit', (code) => {
    console.log(`About to exit with code: ${code}`);
});

module.exports = {

    /**
     * 递归复制目录/文件
     * @param {String} src 源路径
     * @param {String} dest 目标路径
     */
    recursiveCopy: async function (src, dest) {
        if (src.endsWith('.')) {
            return;
        }
        
        var isSrcExists = false;
        try {
            await fsPromise.access(src);
            isSrcExists = true;
        } catch (e) {
            isSrcExists = false;
        }
        var isDestExists = false;
        try {
            await fsPromise.access(dest);
            isDestExists = true;
        } catch (e) {
            isDestExists = false;
        }

        if (isSrcExists) {
            if ((await fsPromise.stat(src)).isDirectory()) { // 复制目录
                if (isDestExists) { // 存在这个路径或者文件
                    if ((await fsPromise.stat(dest)).isDirectory()) { // 是目录就不管它
                        
                    } else { // 不是目录就删掉它
                        await fsPromise.unlink(dest);
                    }
                } else { // 不存在，创建目录
                    await fsPromise.mkdir(dest);
                }
                var files = await fsPromise.readdir(src);
                for (let file of files) {
                    await this.recursiveCopy(src + '/' + file, dest + '/' + file);
                }
            } else { // 复制文件
                if (isDestExists) { // 如果存在目标路径
                    if ((await fsPromise.stat(dest)).isDirectory()) { // 如果目标路径是文件夹就删了
                        await fsPromise.rmdir(dest, {
                            recursive: true
                        });
                    } else { // 删除文件
                        await fsPromise.unlink(dest);
                    }
                } 
                await fsPromise.copyFile(src, dest)
            }
        }
    },

    // 获取前端能识别的cluster.ini配置
    getClusterIniByClusterId: async function (clusterId) {
        var clusterIni = '';
        var clusterIniDic = iniDic.clusterIni;
        try {
            clusterIni = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster.ini', { encoding: 'utf-8' });
        } catch (e) {
            console.log(e);
            return {};
        };

        var lines = clusterIni.split(/\r?\n/);
        var curCategory = null;
        var ret = {};
        for (var line of lines) {
            line = line.trim();
            if ('[' == line.charAt(0) && ']' == line.charAt(line.length - 1)) {
                curCategory = line.substr(1, line.length - 2);
                ret[curCategory] = {};
                continue;
            }
            if (curCategory && line.indexOf('=') !== -1) {
                let spec = line.split('=');
                spec[0] = spec[0].trim();
                spec[1] = spec[1].trim();
                if (clusterIniDic[curCategory] && clusterIniDic[curCategory][spec[0]]) { // 字典中有这个配置项，直接取字典配置，值取spec[1]
                    ret[curCategory][spec[0]] = clusterIniDic[curCategory][spec[0]];
                    ret[curCategory][spec[0]].default = spec[1];
                } else { // 字典中没有这个配置项，使用text
                    ret[curCategory][spec[0]] = {
                        zh: null,
                        type: 'text',
                        default: spec[1]
                    };
                }
            }
        }
        return ret;
    },

    /**
     * 获取正在运行的DstServer服务
     */
    getRunningList: async function () {
        return await new Promise((resolve, reject) => {
            // 先看有没有正在运行的
            var cmdGetRunning = 'ps -ef | grep [d]ontstarve_dedicated_server_nullrenderer';
            // var cmdGetRunning = 'netstat -ano';
            executor.exec(cmdGetRunning, (err, stdout, stderr) => {
                if (err) {
                    
                    // 猜测：输出为空的时候也会进err且code为1,signal为null
                    if (err.code == 1 && err.signal == null) {
                        resolve([]);
                        return;
                    }

                    console.log("=============PSEF ERROR===============");
                    console.log(err);
                    console.log("=============PSEF ERROR===============");

                    // 如果是windows的话直接返回一个空的，开发调试用的
                    if (os.type() == 'Windows_NT') {
                        resolve([]);
                        return;
                    }

                    reject(err);
                    return;
                }
                if (stderr) {
                    console.log(stderr);
                }
                if (!stdout) {
                    resolve([]);
                    return;
                }
                var lines = stdout.split(/\r?\n/);
                var ret = [];
                for (let line of lines) {
                    if (!line) continue;
                    var spec = line.split(/\s+/);
                    var pid = spec[1];
                    var clusterId = null;
                    for (let i = 0; i < spec.length; i++) {
                        if ('-cluster' == spec[i]) {
                            clusterId = spec[i + 1];
                            break;
                        }
                    }
                    ret.push({
                        pid: pid,
                        clusterId: clusterId
                    })
                }
                resolve(ret);
            });
        })
    },

    /**
     * 根据ClusterId启动服务，执行对应路径下的startmaster.sh和startcaves.sh
     * 仅做提交，无法获取其错误
     * @param {String} clusterId 
     */
    runByClusterId: async function (clusterId) {
        var cmdRunMaster = 'bash ' + config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/startmaster.sh';
        var cmdRunCaves = 'bash ' + config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/startcaves.sh';

        if (!global.clusterMapService) {
            global.clusterMapService = {};
        }
        if (!global.clusterMapService[clusterId]) {
            global.clusterMapService[clusterId] = {};
        }

        // 启动地表

        var excMaster = executor.spawn(
            '/bin/sh',
            ['-c', cmdRunMaster],
            {
                cwd: null,
                env: null,
                windowsVerbatimArguments: false
            }
        );
        global.clusterMapService[clusterId].Master = excMaster;
        // var timer = null;
        // setInterval(function () {
        //     try {
        //         excMaster.stdin.write('c_announce("公告测试")\n');
        //     } catch (e) {
        //         console(e);
        //         clearInterval(timer);
        //     }
        // }, 30 * 1000);

        await Promise.all([
            fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-master.log', ''),
            fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-caves.log', '')
        ]);

        excMaster.stdout.on('data', (data) => {
            fsPromise.appendFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-master.log', `stdout: ${data}`);
        });
        excMaster.stderr.on('data', (data) => {
            fsPromise.appendFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-master.log', `stderr: ${data}`);
        });


        // 5 秒后启动洞穴
        setTimeout(function () {
            var excCaves = executor.spawn(
                '/bin/sh',
                ['-c', cmdRunCaves],
                {
                    cwd: null,
                    env: null,
                    windowsVerbatimArguments: false
                }
            );
            global.clusterMapService[clusterId].Caves = excCaves;

            excCaves.stdout.on('data', (data) => {
                fsPromise.appendFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-caves.log', `stdout: ${data}`);
            });
            excCaves.stderr.on('data', (data) => {
                fsPromise.appendFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-caves.log', `stderr: ${data}`);
            });

        }, 5 * 1000);
    },

    /**
     * 根据pid停止服务
     * @param {Array} pid 
     */
    stopByPid: async function (pid) {
        global.clusterMapService = {};

        for (let p of pid) {
            if (!/\d+/.test('' + p)) return;
            var cmdKill = 'kill -9 ' + p;
            await new Promise((resolve, reject) => {
                executor.exec(cmdKill, (err, stdout, stderr) => {
                    if (err) {
                        // 猜测：输出为空的时候也会进err且code为1,signal为null
                        if (err.code == 1 && err.signal == null) {
                            resolve();
                            return;
                        }
                    }
                    if (stderr) {
                        console.log(stderr);
                    }
                    if (stdout) {
                        console.log(stdout);
                    }
                    resolve();
                });
            });
        }
    },

    /**
     * 根据ClusterId停止服务
     * @param {String} clusterId 
     */
    stopByClusterId: async function (clusterId) {
        // if (global.clusterMapService && global.clusterMapService[clusterId]) {
        //     for (service of global.clusterMapService[clusterId]) {
        //         await service.kill('SIGKILL');
        //     }
        //     global.clusterMapService[clusterId] = [];
        // }
        if (global.clusterMapService && global.clusterMapService[clusterId]) {
            global.clusterMapService[clusterId].Master.stdin.write('c_shutdown()\n');
            global.clusterMapService[clusterId].Caves.stdin.write('c_shutdown()\n');
            global.clusterMapService = {};
            return true;
        } else {
            return false;
        }
    },

    /**
     * 构建Cluster.ini文件内容
     * @param {Array} data 
     */
    buildClusterIni: function (data) {

        /*
BASE////==////cluster_dir_name: ""
GAMEPLAY////==////game_mode: "endless"
GAMEPLAY////==////max_players: "4"
GAMEPLAY////==////pvp: "false"
GAMEPLAY////==////pause_when_empty: "false"
GAMEPLAY////==////vote_kick_enabled: "false"
NETWORK////==////lan_only_cluster: "false"
NETWORK////==////cluster_intention: "social"
NETWORK////==////cluster_password: ""
NETWORK////==////cluster_description: ""
NETWORK////==////cluster_name: "Maoda的世界"
NETWORK////==////offline_cluster: "false"
NETWORK////==////cluster_language: "zh"
NETWORK////==////tick_rate: "15"
MISC////==////max_snapshots: "6"
MISC////==////console_enabled: "true"
SHARD////==////shard_enabled: "true"
SHARD////==////bind_ip: "127.0.0.1"
SHARD////==////master_ip: "127.0.0.1"
SHARD////==////master_port: "10889"
SHARD////==////cluster_key: "defaultPass"
STEAM////==////steam_group_only: "false"
STEAM////==////steam_group_id: "false"
STEAM////==////steam_group_admins: "false"

        */
        var clusterItems = {};

        for (var key in data) {
            var keySpec = key.split('////==////');
            if (keySpec[0] == 'BASE') continue;
            if (!clusterItems[keySpec[0]]) {
                clusterItems[keySpec[0]] = {};
            }
            clusterItems[keySpec[0]][keySpec[1]] = data[key];
        }

        var ret = '';

        for (var category in clusterItems) {
            ret += '[' + category + ']\n';
            for (var key in clusterItems[category]) {
                ret += key + ' = ' + clusterItems[category][key] + '\n';
            }
        }
        return ret;
    },


    getModInfo: async function (modId) {

        var types = luajs.types;
        var env = new types.LuaTable();
        var L = luajs.newContext();
        L.loadStdLib();
        
        var content = await fsPromise.readFile(config.modsPath + '/workshop-' + modId + '/modinfo.lua', { encoding: 'utf-8' });
        var ret = {};
        
        try {
            var f = L.loadString(content, env);
            var l = f();
            ret = this.__parseFromLuaTable(env);
        } catch (e) {
            console.error(e);
        }
        ret.__modId = modId;
        return ret;
    },

    getAllModInfo: async function () {
        var dirList = await fsPromise.readdir(config.modsPath);
        var waitList = [];
        for (let dir of dirList) {
            if (dir.match(/^workshop-(\d+)$/)) {
                waitList.push(this.getModInfo(RegExp.$1));
            }
        }
        return await Promise.all(waitList);
    },

    // 获取mod的下载信息，不知道哪里的接口，先借来用用，
    // 到时候搞定了实际下载接口再改
    getModDownInfo: async function (modId) {
        return await new Promise((resolve, reject) => {
            var resBody = '';
            https.get('https://5.5w.pw/api?mid=' + modId, (res) => {
                res.on('data', (data) => {
                    resBody += data;
                });
                res.on('end', () => {
                    resolve(JSON.parse(resBody));
                })
            }).on('error', (e) => {
                reject(e);
            });
        });
    },

    getLevelDataOverride: async function (clusterId, type) {
        
        var types = luajs.types;
        var env = new types.LuaTable();
        var L = luajs.newContext();
        var retData = {};
        if ('Master' == type) {
            retData = iniDic.masterOverrides;
        } else if ('Caves' == type) {
            retData = iniDic.cavesOverrides;
        }
        retData = Object.assign({
            BASE: {
                cluster_dir_name: {
                    zh: '存档ID',
                    type: 'text',
                    default: clusterId,
                    readonly: true
                },
                type: {
                    zh: '世界类型',
                    type: 'text',
                    default: type,
                    readonly: true
                }
            }
        }, retData);

        try {
            var content = ''
            content = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/' + type + '/leveldataoverride.lua', { encoding:  'utf-8' });
            L.loadStdLib();
            var f = L.loadString(content, env);
            var l = f();
            var overrides = this.__parseFromLuaTable(l[0]).overrides;
            console.log("==========READ LEVELDATAOVERRIDE=========");
            for (var key in overrides) {
                for (var tarKey in retData.overrides) {
                    if (key == tarKey) {
                        retData.overrides[tarKey].default = overrides[key] + '';
                    }
                }
            }
        } catch (e) {
            if (e.code == 'ENOENT'){
                return {
                    code: 0,
                    msg: 'success',
                    data: retData
                };
            }
            console.log(e);
            return {
                code: 1,
                msg: '未知错误。'
            };
        }

        return {
            code: 0,
            msg: 'success',
            data: retData
        }
    },

    saveLevelDataOverride: async function (data) {
        var clusterId = data['BASE////==////cluster_dir_name'];
        var type = data['BASE////==////type'];
        if (['Master', 'Caves'].indexOf(type) == -1) {
            return {
                code: 1,
                msg: '非法请求。'
            };
        }
        var content = await fsPromise.readFile('./configtpl/' + type.toLowerCase() + '_leveldataoverride', { encoding: 'utf-8' });
        var saveData = Object.assign({}, iniDic[type.toLowerCase() + 'Overrides'].overrides);
        for (var key in saveData) {
            for (var keykey in data) {
                if (keykey.indexOf('overrides////==////') === 0 && keykey.replace('overrides////==////', '') == key) {
                    saveData[key].default = data[keykey];
                }
            }
        }

        for (var key in saveData) {
            content = content.replace('{{' + key + '}}', saveData[key].default);
        }
        if (content.match(/{{(.+?)}}/)) {
            return {
                code: 1,
                msg: '缺少参数[' + RegExp.$1 + ']'
            };
        }

        await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/' + type + '/leveldataoverride.lua', content);

        return {
            code: 0,
            msg: '操作成功。'
        };
    },

    getClusterMods: async function (clusterId) {
        var types = luajs.types;
        var env = new types.LuaTable();
        var L = luajs.newContext();

        var allMod = await this.getAllModInfo();
        try {
            var content = ''
            content = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Master/modoverrides.lua', { encoding:  'utf-8' });
            L.loadStdLib();
            var f = L.loadString(content, env);
            var l = f();
            var modOverrides = this.__parseFromLuaTable(l[0]);
            for (var mod of allMod) {
                mod.__enabled = false;
                for (var key in modOverrides) {
                    if (modOverrides[key].enabled && 'workshop-' + mod.__modId == key) {
                        mod.__enabled = true;
                    }
                }
            }

            return {
                code: 0,
                msg: 'success',
                data: allMod.sort(function (a, b) {
                    if (a.__enabled && !b.__enabled) {
                        return -1;
                    } else {
                        return 0;
                    }
                })
            }

        } catch (e) {
            if (e.code == 'ENOENT'){
                return {
                    code: 0,
                    msg: 'success',
                    data: allMod
                };
            }
            console.log(e);
            return {
                code: 1,
                msg: '未知错误。'
            };
        }
    },

    saveClusterMods: async function (clusterId, data) {
        var enableStr = [];
        for (var ele of data) {
            enableStr.push(`["workshop-${ele.__modId}"]={ configuration_options={  }, enabled=true }`);
        }
        var content = `return {
    ${enableStr.join(',\n\t')}
}
        `;
        await Promise.all([
            fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Master/modoverrides.lua', content),
            fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Caves/modoverrides.lua', content)
        ]);
        
        return {
            code: 0,
            msg: '操作成功'
        };
    },

    sendCmd: function (clusterId, cmdStr) {
        if (!global.clusterMapService || !global.clusterMapService[clusterId]) {
            return {
                code: 1,
                msg: '当前存档未启动或未通过本程序启动。'
            };
        }
        try {
            global.clusterMapService[clusterId].Master.stdin.write(cmdStr.replace(/\r?\n/g, ' ') + '\n');
        } catch (e) {
            console.log(e);
            return {
                code: 2,
                msg: e
            };
        }
        return {
            code: 0,
            msg: '执行成功。'
        };
    },

    getServerInfo: async function () {
        var retData = {};

        var serverVersion = (await fsPromise.readFile(config.modsPath + '/../version.txt', { encoding:  'utf-8' })).trim();

        retData.serverVersion = serverVersion;

        return {
            code: 0,
            data: retData,
            msg: 'success.'
        }
    },

    checkAndUpdate: function () {
        try {
            if (!global.execResult) {
                global.execResult = {};
            } else if (global.execResult.checkAndUpdate && global.execResult.checkAndUpdate.updating) {
                return {
                    code: 2,
                    msg: '已经有进程正在更新.'
                }
            }
            global.execResult.checkAndUpdate = {
                out: '',
                execHandle: null,
                updating: true
            };
            global.execResult.checkAndUpdate.execHandle = executor.spawn(
                '/bin/sh',
                ['-c', 'bash ' + config.steamCmdPath + '/steamcmd.sh +login anonymous +app_update 343050 validate +quit'],
                {
                    cwd: null,
                    env: null,
                    windowsVerbatimArguments: false
                }
            );
            global.execResult.checkAndUpdate.execHandle.stdout.on('data', (data) => {
                global.execResult.checkAndUpdate.out += `stdout: ${data}\n`;
            });
            global.execResult.checkAndUpdate.execHandle.stderr.on('data', (data) => {
                global.execResult.checkAndUpdate.out += `stderr: ${data}\n`;
            });
            global.execResult.checkAndUpdate.execHandle.on('exit', (code) => {
                global.execResult.checkAndUpdate.updating = false;
                console.log(`checkAndUpdate child process exited with code ${code}`);
            });
        } catch (e) {
            console.error(e);
            return {
                code: 1,
                msg: 'error.'
            }
        }
        return {
            code: 0,
            msg: 'success.'
        }
    },

    checkAndUpdateResult: function () {
        // console.log(global);
        if (global.execResult && global.execResult.checkAndUpdate) {
            return {
                code: 0,
                msg: 'success.',
                updating: global.execResult.checkAndUpdate.updating,
                data: global.execResult.checkAndUpdate.out
            };
        } else {
            return {
                code: 1,
                msg: 'error: empty output.',
            };
        }
    },

    // 把luaTable转成json
    __parseFromLuaTable: function (luaTable) {
        if (luaTable._hashKey == -1) return luaTable.valueOf();
        if (luaTable._hashKey == -2) return luaTable.toString();
        var ret = luaTable;
        if (luaTable.array && luaTable.array.length != 0) { // 非空数组
            ret = [];
            for (let ele of luaTable.array) {
                ret.push(this.__parseFromLuaTable(ele));
            }
        } else if (luaTable.stringMap && Object.keys(luaTable.stringMap).length != 0){ // 非空对象
            ret = {};
            for (key in luaTable.stringMap) {
                ret[key] = this.__parseFromLuaTable(luaTable.stringMap[key][1]);
            }
        } else if (luaTable.array && luaTable.array.length == 0) {
            return [];
        }
        return ret;
    }
}
